# -*- coding: utf-8 -*-

import time
import threading
import multiprocessing as mp

from queue import Queue
from functools import wraps
from concurrent import futures


def namedthread(name=" "):
    """wrap a function into a named threading, which must be of no return
    values, 'cause the decorated function will always return the thread
    """
    def _decorator(func):
        @wraps(func)
        def _deco(*args, **kwargs):
            _t = threading.Thread(target=func, args=args, kwargs=kwargs)
            _t.name = "NamedThread[%s]<0x%x>" % (name, id(_t))
            _t.daemon = True
            _t.start()
            return _t
        return _deco
    return _decorator


def pmap(func, sequence, workers=8, proc=False):
    pool = mp.Pool(workers) if proc else mp.dummy.Pool(workers)
    pool.map(func, sequence)
    pool.close()
    pool.join()


class ThreadPoolWorker(threading.Thread):
    def __init__(self, work_queue, result_queue=None):
        threading.Thread.__init__(self)
        self.work_queue = work_queue
        self.result_queue = result_queue

    def run(self):
        while True:
            try:
                fn, args, kwargs = self.work_queue.get()
                result = fn(*args, **kwargs)
                if self.result_queue:
                    self.result_queue.put(result)
                self.work_queue.task_done()
            except:
                pass


class ThreadPool(object):
    def __init__(self, max_workers, max_tasks):
        self.tasks = Queue(max_tasks)
        self.threads = []
        self.__create_threads(max_workers)

    def __create_threads(self, max_workers):
        for i in range(max_workers):
            t = ThreadPoolWorker(self.tasks)
            t.daemon = True
            t.start()
            self.threads += [t]

    def submit(self, fn, *args, **kwargs):
        self.tasks.put((fn, args, kwargs))

    def wait(self):
        for t in self.threads:
            if t.is_alive():
                t.join()


class TaskPool(futures.ThreadPoolExecutor):
    """A threading pool to hold the tasks, submit high IO operations
    into the pool and register what to do next
    """

    def __init__(self, workers=None, qsize=0, interval=0.0):
        """
        Params:
            - workers: int or None
                max workers of the pool
            - qsize:
                max size of the queue
            - interval: float
                in the function _poll, when the current got future
                is still running, sleep a little time interval
                before getting the next
        """
        super(TaskPool, self).__init__(max_workers=workers)
        self._q = Queue(maxsize=qsize)
        self._t = self._poll()
        self.interval = interval

    def register(self, fn, future):
        """register a function with a future, i.e. bind them together
        Params:
            - fn: Callable
                what to do next. when the future is done, the fn will
                be called, like ``fn = lambda future: future.result``
            - future: Future
        """
        self._q.put((fn, future))

    @namedthread('TaskPool')
    def _poll(self):
        """polling the queue and do with futures those are done, will
        block when the queue is empty
        """
        while True:
            fn, future = self._q.get()
            fn(future) if future.done() else self.register(fn, future)
            time.sleep(self.interval)
